agora inbox for pgsql-hackers@postgresql.org  
help / color / mirror / Atom feed
[PATCH v29 07/11] Add DISTINCT support for IVM
324+ messages / 2 participants
[nested] [flat]

* [PATCH v29 07/11] Add DISTINCT support for IVM
@ 2023-05-31 10:08  Yugo Nagata <nagata@sraoss.co.jp>
  0 siblings, 0 replies; 324+ messages in thread

From: Yugo Nagata @ 2023-05-31 10:08 UTC (permalink / raw)

When IMMV is created with DISTINCT, multiplicity of tuples is
counted and stored in  "__ivm_count__" column, which is a hidden
column of IMMV.  The value in __ivm_count__ is updated when IMMV
is maintained incrementally. A tuple in IMMV can be removed if
and only if the count becomes zero.
---
 src/backend/commands/createas.c     | 141 ++++++++++++++++++++------
 src/backend/commands/indexcmds.c    |  40 ++++++++
 src/backend/commands/matview.c      | 148 ++++++++++++++++++++++++++--
 src/backend/commands/tablecmds.c    |   9 ++
 src/backend/parser/parse_relation.c |  18 +++-
 src/backend/rewrite/rewriteDefine.c |   3 +-
 src/include/commands/createas.h     |   2 +
 src/include/nodes/parsenodes.h      |   1 +
 8 files changed, 317 insertions(+), 45 deletions(-)

diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 415f110516..076f35ee6b 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -53,6 +53,7 @@
 #include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_func.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
@@ -309,6 +310,9 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 					 errhint("functions must be marked IMMUTABLE")));
 
 		check_ivm_restriction((Node *) query);
+
+		/* For IMMV, we need to rewrite matview query */
+		query = rewriteQueryForIMMV(query, into->colNames);
 	}
 
 	if (into->skipData)
@@ -413,6 +417,49 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 	return address;
 }
 
+/*
+ * rewriteQueryForIMMV -- rewrite view definition query for IMMV
+ *
+ * count(*) is added for counting distinct tuples in views.
+ */
+Query *
+rewriteQueryForIMMV(Query *query, List *colNames)
+{
+	Query *rewritten;
+
+	Node *node;
+	ParseState *pstate = make_parsestate(NULL);
+	FuncCall *fn;
+
+	rewritten = copyObject(query);
+	pstate->p_expr_kind = EXPR_KIND_SELECT_TARGET;
+
+	/*
+	 * Convert DISTINCT to GROUP BY and add count(*) for counting distinct
+	 * tuples in views.
+	 */
+	if (rewritten->distinctClause)
+	{
+		TargetEntry *tle;
+
+		rewritten->groupClause = transformDistinctClause(NULL, &rewritten->targetList, rewritten->sortClause, false);
+
+		fn = makeFuncCall(SystemFuncName("count"), NIL, COERCE_EXPLICIT_CALL, -1);
+		fn->agg_star = true;
+
+		node = ParseFuncOrColumn(pstate, fn->funcname, NIL, NULL, fn, false, -1);
+
+		tle = makeTargetEntry((Expr *) node,
+								list_length(rewritten->targetList) + 1,
+								pstrdup("__ivm_count__"),
+								false);
+		rewritten->targetList = lappend(rewritten->targetList, tle);
+		rewritten->hasAggs = true;
+	}
+
+	return rewritten;
+}
+
 /*
  * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
  *
@@ -536,7 +583,8 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 		ColumnDef  *col;
 		char	   *colname;
 
-		if (lc)
+		/* Don't override hidden columns added for IVM */
+		if (lc && !isIvmName(NameStr(attribute->attname)))
 		{
 			colname = strVal(lfirst(lc));
 			lc = lnext(into->colNames, lc);
@@ -940,10 +988,6 @@ check_ivm_restriction_walker(Node *node, void *context)
 					ereport(ERROR,
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 							 errmsg("LIMIT/OFFSET clause is not supported on incrementally maintainable materialized view")));
-				if (qry->distinctClause)
-					ereport(ERROR,
-							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-							 errmsg("DISTINCT is not supported on incrementally maintainable materialized view")));
 				if (qry->hasDistinctOn)
 					ereport(ERROR,
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -1090,12 +1134,18 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel)
 	char		idxname[NAMEDATALEN];
 	List	   *indexoidlist = RelationGetIndexList(matviewRel);
 	ListCell   *indexoidscan;
-	Bitmapset *key_attnos;
 
 	snprintf(idxname, sizeof(idxname), "%s_index", RelationGetRelationName(matviewRel));
 
 	index = makeNode(IndexStmt);
 
+	/*
+	 * We consider null values not distinct to make sure that views with DISTINCT
+	 * or GROUP BY don't contain multiple NULL rows when NULL is inserted to
+	 * a base table concurrently.
+	 */
+	index->nulls_not_distinct = true;
+
 	index->unique = true;
 	index->primary = false;
 	index->isconstraint = false;
@@ -1122,41 +1172,68 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel)
 	index->concurrent = false;
 	index->if_not_exists = false;
 
-	/* create index on the base tables' primary key columns */
-	key_attnos = get_primary_key_attnos_from_query(query, &constraintList);
-	if (key_attnos)
+	if (query->distinctClause)
 	{
+		/* create unique constraint on all columns */
 		foreach(lc, query->targetList)
 		{
 			TargetEntry *tle = (TargetEntry *) lfirst(lc);
 			Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
-
-			if (bms_is_member(tle->resno - FirstLowInvalidHeapAttributeNumber, key_attnos))
-			{
-				IndexElem  *iparam;
-
-				iparam = makeNode(IndexElem);
-				iparam->name = pstrdup(NameStr(attr->attname));
-				iparam->expr = NULL;
-				iparam->indexcolname = NULL;
-				iparam->collation = NIL;
-				iparam->opclass = NIL;
-				iparam->opclassopts = NIL;
-				iparam->ordering = SORTBY_DEFAULT;
-				iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
-				index->indexParams = lappend(index->indexParams, iparam);
-			}
+			IndexElem  *iparam;
+
+			iparam = makeNode(IndexElem);
+			iparam->name = pstrdup(NameStr(attr->attname));
+			iparam->expr = NULL;
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->opclassopts = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
 		}
 	}
 	else
 	{
-		/* create no index, just notice that an appropriate index is necessary for efficient IVM */
-		ereport(NOTICE,
-				(errmsg("could not create an index on materialized view \"%s\" automatically",
-						RelationGetRelationName(matviewRel)),
-				 errdetail("This target list does not have all the primary key columns. "),
-				 errhint("Create an index on the materialized view for efficient incremental maintenance.")));
-		return;
+		Bitmapset *key_attnos;
+
+		/* create index on the base tables' primary key columns */
+		key_attnos = get_primary_key_attnos_from_query(query, &constraintList);
+		if (key_attnos)
+		{
+			foreach(lc, query->targetList)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+				Form_pg_attribute attr = TupleDescAttr(matviewRel->rd_att, tle->resno - 1);
+
+				if (bms_is_member(tle->resno - FirstLowInvalidHeapAttributeNumber, key_attnos))
+				{
+					IndexElem  *iparam;
+
+					iparam = makeNode(IndexElem);
+					iparam->name = pstrdup(NameStr(attr->attname));
+					iparam->expr = NULL;
+					iparam->indexcolname = NULL;
+					iparam->collation = NIL;
+					iparam->opclass = NIL;
+					iparam->opclassopts = NIL;
+					iparam->ordering = SORTBY_DEFAULT;
+					iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+					index->indexParams = lappend(index->indexParams, iparam);
+				}
+			}
+		}
+		else
+		{
+			/* create no index, just notice that an appropriate index is necessary for efficient IVM */
+			ereport(NOTICE,
+					(errmsg("could not create an index on materialized view \"%s\" automatically",
+							RelationGetRelationName(matviewRel)),
+					 errdetail("This target list does not have all the primary key columns, "
+							   "or this view does not contain DISTINCT clause."),
+					 errhint("Create an index on the materialized view for efficient incremental maintenance.")));
+			return;
+		}
 	}
 
 	/* If we have a compatible index, we don't need to create another. */
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ab8b81b302..4811a1c8df 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -38,6 +38,7 @@
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
+#include "commands/matview.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
@@ -1104,6 +1105,45 @@ DefineIndex(Oid tableId,
 	safe_index = indexInfo->ii_Expressions == NIL &&
 		indexInfo->ii_Predicate == NIL;
 
+	/*
+	 * We disallow unique indexes on IVM columns of IMMVs.
+	 */
+	if (RelationIsIVM(rel) && stmt->unique)
+	{
+		for (int i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
+		{
+			AttrNumber	attno = indexInfo->ii_IndexAttrNumbers[i];
+			if (attno > 0)
+			{
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+		}
+
+		if (indexInfo->ii_Expressions)
+		{
+			Bitmapset  *indexattrs = NULL;
+			int			varno = -1;
+
+			pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
+
+			while ((varno = bms_next_member(indexattrs, varno)) >= 0)
+			{
+				int attno = varno + FirstLowInvalidHeapAttributeNumber;
+				char *name = NameStr(TupleDescAttr(rel->rd_att, attno - 1)->attname);
+				if (name && isIvmName(name))
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("unique index creation on IVM columns is not supported")));
+			}
+
+		}
+	}
+
+
 	/*
 	 * Report index creation if appropriate (delay this till after most of the
 	 * error checks)
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 39305f3c49..aa518f20ef 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -152,11 +152,15 @@ static Query *rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *
 
 static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
 			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
-			Query *query);
+			Query *query, bool use_count, char *count_colname);
 static void apply_old_delta(const char *matviewname, const char *deltaname_old,
 				List *keys);
+static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname);
 static void apply_new_delta(const char *matviewname, const char *deltaname_new,
 				StringInfo target_list);
+static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname);
 static char *get_matching_condition_string(List *keys);
 static void generate_equal(StringInfo querybuf, Oid opttype,
 			   const char *leftop, const char *rightop);
@@ -271,6 +275,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	Oid			matviewOid;
 	Relation	matviewRel;
 	Query	   *dataQuery;
+	Query	   *viewQuery;
 	Oid			tableSpace;
 	Oid			relowner;
 	Oid			OIDNewHeap;
@@ -330,8 +335,13 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 						"CONCURRENTLY", "WITH NO DATA")));
 
 
-	dataQuery = get_matview_query(matviewRel);
+	viewQuery = get_matview_query(matviewRel);
 
+	/* For IMMV, we need to rewrite matview query */
+	if (!stmt->skipData && RelationIsIVM(matviewRel))
+		dataQuery = rewriteQueryForIMMV(viewQuery,NIL);
+	else
+		dataQuery = viewQuery;
 
 	/*
 	 * Check that there is a unique index with no WHERE clause on one or more
@@ -511,8 +521,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	if (!stmt->skipData && RelationIsIVM(matviewRel) && !oldPopulated)
 	{
-		CreateIndexOnIMMV(dataQuery, matviewRel);
-		CreateIvmTriggersOnBaseTables(dataQuery, matviewOid);
+		CreateIndexOnIMMV(viewQuery, matviewRel);
+		CreateIvmTriggersOnBaseTables(viewQuery, matviewOid);
 	}
 
 	table_close(matviewRel, NoLock);
@@ -1512,6 +1522,13 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 			int	rte_index = lfirst_int(lc2);
 			TupleDesc		tupdesc_old;
 			TupleDesc		tupdesc_new;
+			bool	use_count = false;
+			char   *count_colname = NULL;
+
+			count_colname = pstrdup("__ivm_count__");
+
+			if (query->distinctClause)
+				use_count = true;
 
 			/* calculate delta tables */
 			calc_delta(table, rte_index, rewritten, dest_old, dest_new,
@@ -1524,7 +1541,8 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
 			{
 				/* apply the delta tables to the materialized view */
 				apply_delta(matviewOid, old_tuplestore, new_tuplestore,
-							tupdesc_old, tupdesc_new, query);
+							tupdesc_old, tupdesc_new, query, use_count,
+							count_colname);
 			}
 			PG_CATCH();
 			{
@@ -1997,7 +2015,7 @@ rewrite_query_for_postupdate_state(Query *query, MV_TriggerTable *table, int rte
 static void
 apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *new_tuplestores,
 			TupleDesc tupdesc_old, TupleDesc tupdesc_new,
-			Query *query)
+			Query *query, bool use_count, char *count_colname)
 {
 	StringInfoData querybuf;
 	StringInfoData target_list_buf;
@@ -2073,7 +2091,12 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		if (rc != SPI_OK_REL_REGISTER)
 			elog(ERROR, "SPI_register failed");
 
-		apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
+		if (use_count)
+			/* apply old delta and get rows to be recalculated */
+			apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME,
+									   keys, count_colname);
+		else
+			apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys);
 
 	}
 	/* For tuple insertion */
@@ -2095,7 +2118,11 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 			elog(ERROR, "SPI_register failed");
 
 		/* apply new delta */
-		apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
+		if (use_count)
+			apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME,
+								keys, &target_list_buf, count_colname);
+		else
+			apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf);
 	}
 
 	/* We're done maintaining the materialized view. */
@@ -2108,6 +2135,51 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, Tuplestorestate *n
 		elog(ERROR, "SPI_finish failed");
 }
 
+/*
+ * apply_old_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_old
+ * which contains tuples to be deleted from to a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct.
+ */
+static void
+apply_old_delta_with_count(const char *matviewname, const char *deltaname_old,
+				List *keys, const char *count_colname)
+{
+	StringInfoData	querybuf;
+	char   *match_cond;
+
+	/* build WHERE condition for searching tuples to be deleted */
+	match_cond = get_matching_condition_string(keys);
+
+	/* Search for matching tuples from the view and update or delete if found. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH t AS ("			/* collecting tid of target tuples in the view */
+						"SELECT diff.%s, "			/* count column */
+								"(diff.%s OPERATOR(pg_catalog.=) mv.%s) AS for_dlt, "
+								"mv.ctid "
+						"FROM %s AS mv, %s AS diff "
+						"WHERE %s"					/* tuple matching condition */
+					"), updt AS ("			/* update a tuple if this is not to be deleted */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.-) t.%s "
+						"FROM t WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND NOT for_dlt "
+					")"
+					/* delete a tuple if this is to be deleted */
+					"DELETE FROM %s AS mv USING t "
+					"WHERE mv.ctid OPERATOR(pg_catalog.=) t.ctid AND for_dlt",
+					count_colname,
+					count_colname, count_colname,
+					matviewname, deltaname_old,
+					match_cond,
+					matviewname, count_colname, count_colname, count_colname,
+					matviewname);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
 /*
  * apply_old_delta
  *
@@ -2157,6 +2229,66 @@ apply_old_delta(const char *matviewname, const char *deltaname_old,
 		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
 }
 
+/*
+ * apply_new_delta_with_count
+ *
+ * Execute a query for applying a delta table given by deltname_new
+ * which contains tuples to be inserted into a materialized view given by
+ * matviewname.  This is used when counting is required, that is, the view
+ * has aggregate or distinct. Also, when a table in EXISTS sub queries
+ * is modified.
+ */
+static void
+apply_new_delta_with_count(const char *matviewname, const char* deltaname_new,
+				List *keys, StringInfo target_list, const char* count_colname)
+{
+	StringInfoData	querybuf;
+	StringInfoData	returning_keys;
+	ListCell	*lc;
+	char	*match_cond = "";
+
+	/* build WHERE condition for searching tuples to be updated */
+	match_cond = get_matching_condition_string(keys);
+
+	/* build string of keys list */
+	initStringInfo(&returning_keys);
+	if (keys)
+	{
+		foreach (lc, keys)
+		{
+			Form_pg_attribute attr = (Form_pg_attribute) lfirst(lc);
+			char   *resname = NameStr(attr->attname);
+			appendStringInfo(&returning_keys, "%s", quote_qualified_identifier("mv", resname));
+			if (lnext(keys, lc))
+				appendStringInfo(&returning_keys, ", ");
+		}
+	}
+	else
+		appendStringInfo(&returning_keys, "NULL");
+
+	/* Search for matching tuples from the view and update if found or insert if not. */
+	initStringInfo(&querybuf);
+	appendStringInfo(&querybuf,
+					"WITH updt AS ("		/* update a tuple if this exists in the view */
+						"UPDATE %s AS mv SET %s = mv.%s OPERATOR(pg_catalog.+) diff.%s "
+						"FROM %s AS diff "
+						"WHERE %s "					/* tuple matching condition */
+						"RETURNING %s"				/* returning keys of updated tuples */
+					") INSERT INTO %s (%s) "	/* insert a new tuple if this doesn't exist */
+						"SELECT %s FROM %s AS diff "
+						"WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);",
+					matviewname, count_colname, count_colname, count_colname,
+					deltaname_new,
+					match_cond,
+					returning_keys.data,
+					matviewname, target_list->data,
+					target_list->data, deltaname_new,
+					match_cond);
+
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+}
+
 /*
  * apply_new_delta
  *
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 47c900445c..adbd768e0d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -53,6 +53,7 @@
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
+#include "commands/matview.h"
 #include "commands/event_trigger.h"
 #include "commands/policy.h"
 #include "commands/sequence.h"
@@ -3673,6 +3674,14 @@ renameatt_internal(Oid myrelid,
 	targetrelation = relation_open(myrelid, AccessExclusiveLock);
 	renameatt_check(myrelid, RelationGetForm(targetrelation), recursing);
 
+	/*
+	 * Don't rename IVM columns.
+	 */
+	if (RelationIsIVM(targetrelation) && isIvmName(oldattname))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("IVM column can not be renamed")));
+
 	/*
 	 * if the 'recurse' flag is set then we are supposed to rename this
 	 * attribute in all classes that inherit from 'relname' (as well as in
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 864ea9b0d5..c257440414 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -36,6 +36,7 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 #include "utils/varlena.h"
+#include "commands/matview.h"
 
 
 /*
@@ -97,7 +98,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 							int count, int offset,
 							int rtindex, int sublevels_up,
 							int location, bool include_dropped,
-							List **colnames, List **colvars);
+							List **colnames, List **colvars, bool is_ivm);
 static int	specialAttNum(const char *attname);
 static bool rte_visible_if_lateral(ParseState *pstate, RangeTblEntry *rte);
 static bool rte_visible_if_qualified(ParseState *pstate, RangeTblEntry *rte);
@@ -1502,6 +1503,7 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -1587,6 +1589,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
+	rte->relisivm = rel->rd_rel->relisivm;
 
 	/*
 	 * Build the list of effective column names using user-supplied aliases
@@ -2758,7 +2761,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 						expandTupleDesc(tupdesc, rte->eref,
 										rtfunc->funccolcount, atts_done,
 										rtindex, sublevels_up, location,
-										include_dropped, colnames, colvars);
+										include_dropped, colnames, colvars, false);
 					}
 					else if (functypclass == TYPEFUNC_SCALAR)
 					{
@@ -3026,7 +3029,7 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
 	expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0,
 					rtindex, sublevels_up,
 					location, include_dropped,
-					colnames, colvars);
+					colnames, colvars, RelationIsIVM(rel));
 	relation_close(rel, AccessShareLock);
 }
 
@@ -3043,7 +3046,7 @@ static void
 expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 				int rtindex, int sublevels_up,
 				int location, bool include_dropped,
-				List **colnames, List **colvars)
+				List **colnames, List **colvars, bool is_ivm)
 {
 	ListCell   *aliascell;
 	int			varattno;
@@ -3056,6 +3059,9 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	{
 		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
 
+		if (is_ivm && isIvmName(NameStr(attr->attname)) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		if (attr->attisdropped)
 		{
 			if (include_dropped)
@@ -3218,6 +3224,10 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 		Var		   *varnode = (Var *) lfirst(var);
 		TargetEntry *te;
 
+		/* if transform * into columnlist with IMMV, remove IVM columns */
+		if (rte->relisivm && isIvmName(label) && !MatViewIncrementalMaintenanceIsEnabled())
+			continue;
+
 		te = makeTargetEntry((Expr *) varnode,
 							 (AttrNumber) pstate->p_next_resno++,
 							 label,
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index e36fc72e1e..f6dc7ba202 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -621,7 +621,8 @@ checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
 														attr->atttypmod))));
 	}
 
-	if (i != resultDesc->natts)
+	/* No check for materialized views since this could have special columns for IVM */
+	if ((!isSelect || requireColumnNameMatch) && i != resultDesc->natts)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 				 isSelect ?
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index 09a64fa2e5..76a7873ebf 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -29,6 +29,8 @@ extern ObjectAddress ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *st
 extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid matviewOid);
 extern void CreateIndexOnIMMV(Query *query, Relation matviewRel);
 
+extern Query *rewriteQueryForIMMV(Query *query, List *colNames);
+
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
 
 extern DestReceiver *CreateIntoRelDestReceiver(IntoClause *intoClause);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index fef4c714b8..1a2b8fa09e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1073,6 +1073,7 @@ typedef struct RangeTblEntry
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
 	Index		perminfoindex;
+	bool		relisivm;
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
-- 
2.25.1


--Multipart=_Mon__28_Aug_2023_16_05_30_+0900_b1OvQD_3A3ZMTGvj
Content-Type: text/x-diff;
 name="v29-0008-Add-aggregates-support-in-IVM.patch"
Content-Disposition: attachment;
 filename="v29-0008-Add-aggregates-support-in-IVM.patch"
Content-Transfer-Encoding: quoted-printable



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v44 07/10] Split cluster.h to create repack_internal.h
@ 2026-03-24 14:09  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 324+ messages in thread

From: Álvaro Herrera @ 2026-03-24 14:09 UTC (permalink / raw)

Most of the details of concurrent repack are only needed by
pgoutput_repack; and they have a nasty effect on headers included by
cluster.h (used by several high-profile places), so isolate that for
cleanliness.

Also, change_useless_for_repack() is better declared in decode.h.
---
 src/backend/commands/cluster.c                | 128 ++++++++++--------
 src/backend/postmaster/bgworker.c             |   2 +-
 .../pgoutput_repack/pgoutput_repack.c         |  13 +-
 src/include/commands/cluster.h                |  57 +-------
 src/include/commands/repack_internal.h        |  59 ++++++++
 src/include/replication/decode.h              |   4 +
 6 files changed, 147 insertions(+), 116 deletions(-)
 create mode 100644 src/include/commands/repack_internal.h

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 75556cbdafb..2c3058ba10d 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -59,6 +59,7 @@
 #include "commands/cluster.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/repack_internal.h"
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -205,17 +206,11 @@ typedef struct DecodingWorkerShared
 	char		error_queue[FLEXIBLE_ARRAY_MEMBER];
 } DecodingWorkerShared;
 
-/*
- * Generate worker's output file name. If relations of the same 'relid' happen
- * to be processed at the same time, they must be from different databases and
- * therefore different backends must be involved. (PID is already present in
- * the fileset name.)
- */
-static inline void
-DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
-{
-	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
-}
+/* Is this process a REPACK worker? */
+static bool is_repack_worker = false;
+
+static pid_t backend_pid;
+static ProcNumber backend_proc_number;
 
 /*
  * Backend-local information to control the decoding worker.
@@ -241,6 +236,7 @@ static DecodingWorker *decoding_worker = NULL;
  */
 volatile sig_atomic_t RepackMessagePending = false;
 
+static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
 								int options);
@@ -298,12 +294,16 @@ static Relation process_single_relation(RepackStmt *stmt,
 										ClusterParams *params);
 static Oid	determine_clustered_index(Relation rel, bool usingindex,
 									  const char *indexname);
-static void start_decoding_worker(Oid relid);
-static void stop_decoding_worker(void);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
 static void repack_worker_internal(dsm_segment *seg);
 static void export_initial_snapshot(Snapshot snapshot,
 									DecodingWorkerShared *shared);
 static Snapshot get_initial_snapshot(DecodingWorker *worker);
+extern bool am_decoding_for_repack(void);
+static void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
 static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
@@ -369,17 +369,8 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 					parser_errposition(pstate, opt->location));
 	}
 
-	/*
-	 * Determine the lock mode expected by cluster_rel().
-	 *
-	 * In the exclusive case, we obtain AccessExclusiveLock right away to
-	 * avoid lock-upgrade hazard in the single-transaction case. In the
-	 * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end
-	 * of processing, supposedly for very short time. Until then, we'll have
-	 * to unlock the relation temporarily, so there's no lock-upgrade hazard.
-	 */
-	lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ?
-		AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
@@ -434,6 +425,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 										   "Repack",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Since we open a new transaction for each relation, we have to check
+	 * that the relation still is what we think it is.
+	 *
+	 * In single-transaction CLUSTER, we don't need the overhead.
+	 */
 	params.options |= CLUOPT_RECHECK;
 
 	/*
@@ -544,6 +541,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	MemoryContextDelete(repack_context);
 }
 
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards.  In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+	if (concurrent)
+		return ShareUpdateExclusiveLock;
+	else
+		return AccessExclusiveLock;
+}
+
 /*
  * cluster_rel
  *
@@ -583,25 +596,22 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
 
-	/*
-	 * The lock mode is AccessExclusiveLock for normal processing and
-	 * ShareUpdateExclusiveLock for concurrent processing (so that SELECT,
-	 * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be
-	 * called concurrently for the same relation).
-	 */
-	lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock;
+	/* Determine the lock mode to use. */
+	lmode = RepackLockLevel(concurrent);
 
-	/* There are specific requirements on concurrent processing. */
+	/*
+	 * Check some preconditions in the concurrent case.  This also obtains the
+	 * replica index OID.
+	 */
 	if (concurrent)
 	{
 		/*
-		 * Make sure we have no XID assigned, otherwise call of
-		 * repack_setup_logical_decoding() can cause a deadlock.
+		 * Make sure we're not in a transaction block.
 		 *
-		 * The existence of transaction block actually does not imply that XID
-		 * was already assigned, but it very likely is. We might want to check
-		 * the result of GetCurrentTransactionIdIfAny() instead, but that
-		 * would be less clear from user's perspective.
+		 * The reason is that repack_setup_logical_decoding() could deadlock
+		 * if there's an XID already assigned.  It would be possible to run in
+		 * a transaction block if we had no XID, but this restriction is
+		 * simpler for users to understand and we don't lose anything.
 		 */
 		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 
@@ -626,15 +636,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	RestrictSearchPath();
 
 	/*
-	 * Since we may open a new transaction for each relation, we have to check
-	 * that the relation still is what we think it is.
+	 * Recheck that the relation is still what it was when we started.
 	 *
-	 * If this is a single-transaction CLUSTER, we can skip these tests. We
-	 * *must* skip the one on indisclustered since it would reject an attempt
-	 * to cluster a not-previously-clustered index.
-	 *
-	 * XXX move [some of] these comments to where the RECHECK flag is
-	 * determined?
+	 * Note that it's critical to skip this in single-relation CLUSTER;
+	 * otherwise, we would reject an attempt to cluster using a
+	 * not-previously-clustered index.
 	 */
 	if (recheck &&
 		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
@@ -754,7 +760,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 			 * However it still seems a good practice to make sure that the
 			 * worker never survives the REPACK command.
 			 */
-			stop_decoding_worker();
+			stop_repack_decoding_worker();
 		}
 	}
 	PG_END_TRY();
@@ -1093,7 +1099,7 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 		 * clustering index) and checking again if it's still eligible for
 		 * REPACK CONCURRENTLY.
 		 */
-		start_decoding_worker(tableOid);
+		start_repack_decoding_worker(tableOid);
 
 		/*
 		 * Wait until the worker has the initial snapshot and retrieve it.
@@ -2538,7 +2544,6 @@ RepackCommandAsString(RepackCommand cmd)
 	return "???";				/* keep compiler quiet */
 }
 
-
 /*
  * Is this backend performing logical decoding on behalf of REPACK
  * (CONCURRENTLY) ?
@@ -3688,7 +3693,7 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
  * contents to a new table.
  */
 static void
-start_decoding_worker(Oid relid)
+start_repack_decoding_worker(Oid relid)
 {
 	Size		size;
 	dsm_segment *seg;
@@ -3779,7 +3784,7 @@ start_decoding_worker(Oid relid)
  * we need to stop it explicitly at least on ERROR in the launching backend.
  */
 static void
-stop_decoding_worker(void)
+stop_repack_decoding_worker(void)
 {
 	BgwHandleStatus status;
 
@@ -3817,12 +3822,6 @@ stop_decoding_worker(void)
 	decoding_worker = NULL;
 }
 
-/* Is this process a REPACK worker? */
-static bool is_repack_worker = false;
-
-static pid_t backend_pid;
-static ProcNumber backend_proc_number;
-
 /*
  * See ParallelWorkerShutdown for details.
  */
@@ -3869,7 +3868,7 @@ RepackWorkerMain(Datum main_arg)
 
 	/*
 	 * Join locking group - see the comments around the call of
-	 * start_decoding_worker().
+	 * start_repack_decoding_worker().
 	 */
 	if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
 		/* The leader is not running anymore. */
@@ -4039,6 +4038,18 @@ get_initial_snapshot(DecodingWorker *worker)
 	return snapshot;
 }
 
+/*
+ * Generate worker's output file name. If relations of the same 'relid' happen
+ * to be processed at the same time, they must be from different databases and
+ * therefore different backends must be involved. (PID is already present in
+ * the fileset name.)
+ */
+static void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
 bool
 IsRepackWorker(void)
 {
@@ -4067,7 +4078,6 @@ void
 ProcessRepackMessages(void)
 {
 	MemoryContext oldcontext;
-
 	static MemoryContext hpm_context = NULL;
 
 	/*
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index f8a8d1681e9..9e876d55e27 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
index 032fbd0e5b0..cc9ce615b18 100644
--- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -13,7 +13,7 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "commands/cluster.h"
+#include "commands/repack_internal.h"
 #include "replication/snapbuild.h"
 #include "utils/memutils.h"
 
@@ -176,7 +176,7 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 					ConcurrentChangeKind kind, HeapTuple tuple)
 {
 	RepackDecodingState *dstate;
-	MemoryContext	oldcxt;
+	MemoryContext oldcxt;
 	BufFile    *file;
 	List	   *attrs_ext = NIL;
 	int			natt_ext;
@@ -226,7 +226,10 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 
 			slot_getsomeattrs(slot, i + 1);
 
-			/* This is a non-null varlena datum, but we only care if it's out-of-line */
+			/*
+			 * This is a non-null varlena datum, but we only care if it's
+			 * out-of-line
+			 */
 			varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
 			if (!VARATT_IS_EXTERNAL(varlen))
 				continue;
@@ -244,8 +247,8 @@ repack_store_change(LogicalDecodingContext *ctx, Relation relation,
 				 * attributes (those actually should never appear on disk), so
 				 * only TOASTed attribute can be seen here.
 				 *
-				 * FIXME in what circumstances can an ONDISK attr appear?
-				 * Why aren't these written separately?
+				 * FIXME in what circumstances can an ONDISK attr appear? Why
+				 * aren't these written separately?
 				 */
 				Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
 			}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 1c0ac3ab4f5..1528d34fa42 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -13,17 +13,12 @@
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
-#include "nodes/execnodes.h"
+#include <signal.h>
+
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
-#include "replication/decode.h"
-#include "postmaster/bgworker.h"
-#include "replication/logical.h"
-#include "storage/buffile.h"
 #include "storage/lockdefs.h"
-#include "storage/shm_mq.h"
 #include "utils/relcache.h"
-#include "utils/resowner.h"
 
 
 /* flag bits for ClusterParams->options */
@@ -34,55 +29,14 @@
 #define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 #define CLUOPT_CONCURRENT 0x10	/* allow concurrent data changes */
 
-
 /* options for CLUSTER */
 typedef struct ClusterParams
 {
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-
-/*
- * The following definitions are used by REPACK CONCURRENTLY.
- */
-
-/*
- * Stored as a single byte in the output file.
- */
-#define		CHANGE_INSERT		'i'
-#define		CHANGE_UPDATE_OLD	'u'
-#define		CHANGE_UPDATE_NEW	'U'
-#define		CHANGE_DELETE		'd'
-typedef char ConcurrentChangeKind;
-
-/*
- * Logical decoding state.
- *
- * The output plugin uses it to store the data changes that it decodes from
- * WAL while the table contents is being copied to a new storage.
- */
-typedef struct RepackDecodingState
-{
-#ifdef	USE_ASSERT_CHECKING
-	/* The relation whose changes we're decoding. */
-	Oid			relid;
-#endif
-
-	/* Per-change memory context. */
-	MemoryContext	change_cxt;
-
-	/* A tuple slot used to pass tuples back and forth */
-	TupleTableSlot	*slot;
-
-	/* The current output file. */
-	BufFile    *file;
-} RepackDecodingState;
-
 extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
 
-extern bool IsRepackWorker(void);
-extern void HandleRepackMessageInterrupt(void);
-extern void ProcessRepackMessages(void);
 
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
@@ -104,8 +58,9 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern bool am_decoding_for_repack(void);
-extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+extern bool IsRepackWorker(void);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
 
-extern void RepackWorkerMain(Datum main_arg);
 #endif							/* CLUSTER_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..f90c973f5a0
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ *	  header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+#define		CHANGE_INSERT		'i'
+#define		CHANGE_UPDATE_OLD	'u'
+#define		CHANGE_UPDATE_NEW	'U'
+#define		CHANGE_DELETE		'd'
+typedef char ConcurrentChangeKind;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef	USE_ASSERT_CHECKING
+	/* The relation whose changes we're decoding. */
+	Oid			relid;
+#endif
+
+	/* Per-change memory context. */
+	MemoryContext change_cxt;
+
+	/* A tuple slot used to pass tuples back and forth */
+	TupleTableSlot *slot;
+
+	/* The current output file. */
+	BufFile    *file;
+} RepackDecodingState;
+
+
+extern void RepackWorkerMain(Datum main_arg);
+
+#endif							/* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 49f00fc48b8..02b5393474c 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -31,4 +31,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
 extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
 										 XLogReaderState *record);
 
+/* in commands/cluster.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
 #endif
-- 
2.47.3


--gwom7bl7ogtszo4k
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v44-0008-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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


end of thread, other threads:[~2026-03-24 14:09 UTC | newest]

Thread overview: 324+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2023-05-31 10:08 [PATCH v29 07/11] Add DISTINCT support for IVM Yugo Nagata <nagata@sraoss.co.jp>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Álvaro Herrera <alvherre@kurilemu.de>
2026-03-24 14:09 [PATCH v44 07/10] Split cluster.h to create repack_internal.h Á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